package websocket

import (
	"encoding/json"
	"net/http"
	"strconv"
	"strings"
	"sync"
	"time"

	"mall/common/globalkey"

	"github.com/gorilla/websocket"
)

// https://github.com/zeromicro/zero-examples/blob/main/chat/internal/hub.go
type (
	Node struct {
		UserId uint64 `json:"userId"`
		conn   *websocket.Conn
		data   chan []byte
	}

	ChatClient interface {
		WritePump(wg *sync.WaitGroup)
		ReadPump(wg *sync.WaitGroup)
		SendMsg(msg *Message) error
	}
)

var (
	userProcessorCollector = sync.Map{}
)

const (
	// send buffer size
	bufSize = 256
)

func NewChatClient(userId uint64, w http.ResponseWriter, r *http.Request) ChatClient {
	if n, ok := userProcessorCollector.Load(userId); ok {
		return n.(*Node)
	}
	upgrader := websocket.Upgrader{
		ReadBufferSize:  1024,
		WriteBufferSize: 1024,
		CheckOrigin: func(r *http.Request) bool {
			return true
		},
	}
	conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		return nil
	}
	node := &Node{
		UserId: userId,
		conn:   conn,
		data:   make(chan []byte),
	}
	userProcessorCollector.Store(userId, node)

	return node
}

type Message struct {
	MsgType  *uint32 `json:"msgType"`
	FriendId *string `json:"friendId"`
	Msg      *string `json:"msg"`
}

func (l *Node) SendMsg(msg *Message) error {
	mal, err := json.Marshal(msg)
	if err != nil {
		return err
	}
	err = l.conn.WriteMessage(websocket.TextMessage, mal)
	if err != nil {
		return err
	}
	return nil
}

func (l *Node) WritePump(wg *sync.WaitGroup) {
	ticker := time.NewTicker(pingPeriod)
	defer func() {
		ticker.Stop()
		err := l.conn.Close()
		if err != nil {
			return
		}
		wg.Done()
	}()
	for {
		select {
		case data, ok := <-l.data:
			l.conn.SetWriteDeadline(time.Now().Add(writeWait))
			if !ok {
				l.conn.WriteMessage(websocket.CloseMessage, []byte{})
				return
			}
			l.conn.WriteMessage(websocket.TextMessage, data)
		case <-ticker.C:
			l.conn.SetWriteDeadline(time.Now().Add(writeWait))
			if err := l.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
				return
			}
		}
	}
}

func (l *Node) ReadPump(wg *sync.WaitGroup) {
	defer func() {
		err := l.conn.Close()
		if err != nil {
			return
		}
		close(l.data)
		userProcessorCollector.Delete(l.UserId)
		wg.Done()
	}()
	for {
		_, message, err := l.conn.ReadMessage()
		if err != nil {
			//node.webCon.Close()
			return
		}
		// 转发给接收者
		sendRevice(message)
	}
}

func sendRevice(msg []byte) {
	var message = &Message{}
	json.Unmarshal(msg, message)
	if message.Msg != nil && len(strings.TrimSpace(*message.Msg)) > 0 {
		//微信内容审核
		//err = l.svcCtx.MiniProgram.GetContentSecurity().CheckText(*req.PostContent)
		//if err != nil {
		//	return nil, resultx.NewErrCode(resultx.FORUM_POST_CONTENT_INVALID)
		//}
		if message.MsgType != nil {
			if *message.MsgType == globalkey.ChatMessageTypeSys {
				sendSysMessage(msg)
			}
			if *message.MsgType == globalkey.ChatMessageTypeGroup && message.FriendId != nil && len(strings.TrimSpace(*message.FriendId)) > 4 {
				var friendIds []uint64
				for _, v := range strings.Split(*message.FriendId, ",") {
					u, err := strconv.ParseUint(v, 10, 64)
					if err != nil {
						return
					}
					friendIds = append(friendIds, u)
				}
				sendGroupMessage(msg, friendIds)
			}
			if (*message.MsgType == globalkey.ChatMessageTypePrivate || *message.MsgType == globalkey.ChatMessageTypeSysShoppingCartUpdated) && message.FriendId != nil && len(strings.TrimSpace(*message.FriendId)) > 0 {
				u, err := strconv.ParseUint(*message.FriendId, 10, 64)
				if err != nil {
					return
				}
				sendPrivateMessage(msg, u)
			}
		}
	}
}

func sendSysMessage(msg []byte) {
	userProcessorCollector.Range(func(key, value interface{}) bool {
		node := value.(*Node)
		if node.data != nil {
			node.data <- msg
		}
		return true
	})
}
func sendGroupMessage(msg []byte, friendIds []uint64) {
	for _, id := range friendIds {
		if v, ok := userProcessorCollector.Load(id); ok {
			node := v.(*Node)
			if node != nil {
				node.data <- msg
			}
		}
	}
}
func sendPrivateMessage(msg []byte, friendId uint64) {
	if v, ok := userProcessorCollector.Load(friendId); ok {
		node := v.(*Node)
		if node != nil {
			node.data <- msg
		}
	}
}
